import {
  Component,
  Input,
  Output,
  Inject,
  Renderer2,
  forwardRef,
  ViewChild,
  EventEmitter,
  ElementRef,
  ViewContainerRef,
  TemplateRef,
  ChangeDetectionStrategy,
  ViewEncapsulation,
  ChangeDetectorRef,
  Optional,
  SimpleChanges,
  OnChanges,
  OnInit,
  OnDestroy,
} from '@angular/core';
import { Router } from '@angular/router';
import { trigger, state, style, transition, animate, AnimationEvent, group } from '@angular/animations';
import { TemplatePortal } from '@angular/cdk/portal';
import { Overlay, OverlayConfig } from '@angular/cdk/overlay';
import { merge, Subject } from 'rxjs';
import { auditTime, takeUntil, distinctUntilChanged } from 'rxjs/operators';
import { MenuBaseService } from '../core/services/api';
import { ExtendOverlayConfig, OverlayService } from '../core/overlay/api';
import { InputNumber, routerNavigate, TypeUtils } from '../core/utils/api';
import { MotionDuration, MotionFunc } from '../core/animation/api';
import { MenuItem } from './interface';
import { MenuService } from './menu.service';

@Component({
  selector: 'lv-menu',
  template: `
    <div class="lv-menu-title" *ngIf="lvTitle">
      <ng-container *ngIf="typeUtils.isRealString(lvTitle)">{{ lvTitle }}</ng-container>
      <ng-container *ngIf="typeUtils.isTemplateRef(lvTitle)">
        <ng-container *ngTemplateOutlet="lvTitle"></ng-container>
      </ng-container>
    </div>
    <ul
      lv-submenu
      [lvMenuList]="lvMenus"
      [lvCurrentId]="lvActiveItemId"
      [lvCurrentHoverId]="hoverItemId"
      class="level0"
      [lvMenuLevel]="0"
    ></ul>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class.lv-menu]': 'true',
    '[class.lv-left-menu]': 'lvDirection === "vertical"',
    '[class.lv-top-menu]': 'lvDirection === "horizontal"',
    '[class.lv-menu-icon]': 'lvMode === "icon"',
    '[class.lv-menu-text]': 'lvMode === "text"',
    '[class.lv-theme-dark]': 'lvTheme === "dark"',
    '[class.lv-menu-collapsed]': 'lvCollapsed === true',
  },
})
export class MenuComponent implements OnChanges {
  @Input() lvMenus: MenuItem[];
  @Input() lvPanelClass: string;
  @Input() lvCollapsed: boolean = false;
  @Input() @InputNumber() lvPanelColumns;
  @Input() lvMode: 'icon' | 'text' = 'icon';
  @Input() lvItemClickClose: boolean = true; // 在纵向菜单折叠状态和横向菜单场景下，点击菜单项时关闭菜单弹层
  @Input() lvMultiExpansion: boolean = false;

  @Input() lvLabelTemplate: TemplateRef<void>;

  @Input() lvTheme: 'light' | 'dark' = 'dark';

  @Input() lvTitle: string | TemplateRef<void> = '';

  @Output() readonly lvItemClick = new EventEmitter<any>();

  @Input() lvDirection: 'vertical' | 'horizontal' = 'vertical';

  @Output() readonly lvMenusChange = new EventEmitter<MenuItem[]>();

  @Input() lvMenuPanelAlignment: 'left' | 'center' | 'right' = 'left';

  @Output() readonly lvActiveItemIdChange = new EventEmitter<boolean>();

  public typeUtils = TypeUtils;

  public collapsedChange$ = new Subject<void>();

  private _nav;

  private _tempMenu;

  private _hoverItemId;
  private _activeItemId;

  private _activeParentIds;

  constructor(
    private el: ElementRef,
    private renderer: Renderer2,
    private cdr: ChangeDetectorRef,
  ) {}

  get hoverItemId(): any {
    return this._hoverItemId;
  }

  get lvActiveItemId(): any {
    return this._activeItemId;
  }

  @Input() set lvActiveItemId(val: any) {
    this.reset('click');
    this.getItemPath('click', val);
    this._activeItemId = val;
    this.cdr.markForCheck();
  }

  set hoverItemId(val: any) {
    this.reset('hover');
    if (val) {
      this.getItemPath('hover', val);
      this._hoverItemId = val;
    }
  }

  closeNav(): void {
    this._nav?.close();
  }

  getParentId(id): void {
    const p = this._tempMenu.find((i): boolean => i.id === id && i.parentId !== null);

    if (p === undefined) {
      return;
    }

    this._activeParentIds.push(p.parentId);
    this.getParentId(p.parentId);
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.lvMenus) {
      this.reset('click');
      this.getItemPath('click', this.lvActiveItemId);
      this.lvMenusChange.emit(this.lvMenus);
    }
    if (changes.lvCollapsed) {
      this.collapsedChange$.next();
    }
  }

  flatMenus(data, parentId): void {
    if (data) {
      data.forEach((item): void => {
        const _item = {
          id: item.id,
          parentId,
          routerLink: item.routerLink || '',
        };
        this._tempMenu.push(_item);

        if (item.items?.length) {
          this.flatMenus(item.items, item.id);
        }
      });
    }
  }

  setHoverItem(value, data?): void {
    let d = data;
    d = d || this.lvMenus || [];

    for (const item of d) {
      if (!item || !item.id) {
        continue;
      }

      if (item.id === value) {
        item._parentHover = true;
        break;
      } else if (item.items?.length) {
        this.setHoverItem(value, item.items);
      } else {
        continue;
      }
    }
  }

  getItemPath(event, id): void {
    this._tempMenu = [];
    this._activeParentIds = [];

    this.flatMenus(this.lvMenus, null);
    this.getParentId(id);

    if (event === 'click') {
      this.setActiveItem(id, true);
      this._activeParentIds.forEach((item): void => {
        this.setActiveItem(item, false);
      });
    }

    if (event === 'hover') {
      this._activeParentIds.forEach((item): void => {
        this.setHoverItem(item);
      });
    }
  }

  reset(event, data?): void {
    let d = data;
    d = d || this.lvMenus;
    if (d) {
      d.forEach((item): void => {
        if (event === 'click' && item._active) {
          item._active = false;
        }

        if (event === 'click' && item._parentActive) {
          item._parentActive = false;
        }

        if (event === 'hover' && item._parentHover) {
          item._parentHover = false;
        }

        if (item.items) {
          this.reset(event, item.items);
        }
      });
    }
  }

  setActiveItem(value, active: boolean, data?): void {
    let d = data;
    d = d || this.lvMenus || [];

    for (const item of d) {
      if (!item || !item.id) {
        continue;
      }

      if (item.id === value) {
        if (active) {
          item.active = true;
        } else {
          item.expanded = true;
          item._parentActive = true;
        }
        break;
      } else if (item.items?.length) {
        if (!this.lvMultiExpansion) {
          item.expanded = false;
        }
        item._parentActive = false;
        this.setActiveItem(value, active, item.items);
      } else {
        continue;
      }
    }
  }
}

@Component({
  selector: '[lv-submenu]',
  providers: [MenuBaseService, MenuService],
  templateUrl: './menu.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  host: {},
  animations: [
    trigger('expandItem', [
      state(
        'hidden',
        style({
          height: '0px',
        }),
      ),
      state(
        'visible',
        style({
          height: '*',
        }),
      ),
      transition('visible => hidden', [
        group([
          animate(
            `${MotionDuration.MD} ${MotionFunc.EASE_IN_OUT}`,
            style({
              opacity: 0,
            }),
          ),
          animate(
            `${MotionDuration.MD} ${MotionFunc.EASE}`,
            style({
              height: '0px',
            }),
          ),
        ]),
      ]),
      transition('hidden => visible', [
        group([
          animate(
            `${MotionDuration.LG} ${MotionFunc.EASE_IN_OUT}`,
            style({
              opacity: 1,
            }),
          ),
          animate(
            `${MotionDuration.LG} ${MotionFunc.EASE}`,
            style({
              height: '*',
            }),
          ),
        ]),
      ]),
    ]),
    trigger('showItem', [
      state(
        'void',
        style({
          transform: 'scale(0.9) translate3d(0, 0, 0)',
          opacity: 0,
        }),
      ),
      state(
        'hide',
        style({
          transform: 'scale(0.9) translate3d(0, 0, 0)',
          opacity: 0,
        }),
      ),
      state(
        'show',
        style({
          transform: 'scale(1) translate3d(0, 0, 0)',
          opacity: 1,
        }),
      ),
      transition('hide => show', animate(`${MotionDuration.MD} ${MotionFunc.EASE_IN_OUT}`)),
      transition('show => hide', animate(`${MotionDuration.LG} ${MotionFunc.EASE_IN_OUT}`)),
    ]),
    trigger('slideMenu', [
      state(
        'void',
        style({
          transform: 'translateY(-100%)',
          transformOrigin: '50% 0%',
          opacity: 0,
        }),
      ),
      state(
        'hide',
        style({
          transform: 'translateY(-100%)',
          transformOrigin: '50% 0%',
          opacity: 0,
        }),
      ),
      state(
        'show',
        style({
          transform: 'translateY(0%)',
          transformOrigin: '50% 0%',
          opacity: 1,
        }),
      ),
      transition('hide => show', animate(`${MotionDuration.MD} ${MotionFunc.EASE}`)),
      transition('show => hide', animate(`${MotionDuration.MD} ${MotionFunc.EASE}`)),
    ]),
    trigger('rotateIcon', [
      state(
        'open',
        style({
          transform: 'rotate(180deg)',
        }),
      ),
      state(
        'close',
        style({
          transform: 'rotate(0)',
        }),
      ),
      transition('open => close', [
        group([
          animate(
            `${MotionDuration.XS} ${MotionFunc.EASE}`,
            style({
              transform: 'scaleX(1.2)',
            }),
          ),
          animate(`${MotionDuration.XS} ${MotionFunc.EASE_IN_OUT}`, style({})),
        ]),
      ]),
      transition('close => open', [
        group([
          animate(
            `${MotionDuration.SM} ${MotionFunc.EASE}`,
            style({
              transform: 'scaleX(1.2)',
            }),
          ),
          animate(`${MotionDuration.SM} ${MotionFunc.EASE_IN_OUT}`, style({})),
        ]),
      ]),
    ]),
  ],
})
export class SubmenuComponent implements OnInit, OnDestroy {
  @Input() lvMenuLevel: any;

  @Input() lvCurrentId: string;

  @Input() lvMenuList: MenuItem[];

  @Input() lvCurrentHoverId: string;

  @ViewChild('templatePortal', { static: false })
  menuPanelTemplate: TemplateRef<any>;

  public _menu;
  public _animationState;
  public typeUtils = TypeUtils;
  private _overlay;
  private destroy$ = new Subject<void>();
  private openState$ = new Subject<boolean>();

  private MENU_PANEL_POSITION: any = [
    // Vertical Menu
    [{ originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top' }],
    // Horizontal Menu (level less than 2)
    [
      {
        originX: 'start',
        originY: 'bottom',
        overlayX: 'start',
        overlayY: 'top',
      },
      {
        originX: 'center',
        originY: 'bottom',
        overlayX: 'center',
        overlayY: 'top',
      },
      { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top' },
    ],
    // Horizontal Menu (others level)
    [
      { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top' },
      { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top' },
    ],
  ];

  constructor(
    private el: ElementRef,
    private renderer: Renderer2,
    private cdr: ChangeDetectorRef,
    @Optional() public router: Router,
    private overlay: Overlay,
    private overlayService: OverlayService,
    private viewContainerRef: ViewContainerRef,
    private menuService: MenuService,
    private menuBaseService: MenuBaseService,
    @Inject(forwardRef((): typeof MenuComponent => MenuComponent)) menu: MenuComponent,
  ) {
    this._menu = menu;
  }

  ngOnDestroy(): void {
    if (this._overlay?.overlayRef.hasAttached()) {
      this._overlay.overlayRef.dispose();
    }

    this.destroy$.next();
    this.destroy$.complete();
  }

  getExpandItemState(child): string {
    if (this._menu.lvDirection === 'horizontal') {
      return '';
    } else if (child.expanded) {
      return 'visible';
    } else {
      return 'hidden';
    }
  }

  onAnimationDone(event: AnimationEvent): void {
    if (event.fromState === 'show' && event.toState === 'hide') {
      this.openState$.next(false);
    }
    if (event.toState === 'show') {
      this.openState$.next(true);
    }
  }

  collapseOthers(activeItem): void {
    this.lvMenuList.forEach((item): void => {
      if (item.items) {
        if (item.id === activeItem.id) {
          item.expanded = !item.expanded;
        } else {
          item.expanded = false;
        }
      }
    });
  }

  ngOnInit(): void {
    this.menuService.menuOpen$.pipe(takeUntil(this.destroy$)).subscribe((): void => {});

    this.menuService.open$
      .pipe(
        // NEED_DO 兼容issue #263  所以加上横向的判断
        auditTime(this._menu.lvDirection === 'horizontal' ? 0 : 50),
        distinctUntilChanged(),
        takeUntil(this.destroy$),
      )
      .subscribe((open): void => {
        if (!open) {
          if (this._overlay?.overlayRef.hasAttached()) {
            this._overlay?.close();
          }
          this._menu.hoverItemId = null;
        }
      });

    // tslint:disable-next-line
    merge(this.menuService.open$, this.menuService.mouseEnter$, this.openState$, this._menu.collapsedChange$)
      .pipe(takeUntil(this.destroy$))
      .subscribe((): void => {
        this.cdr.markForCheck();
      });
  }

  setMouseEnterState(event, item, value: boolean): void {
    if (this._menu.lvDirection === 'vertical' && !this._menu.lvCollapsed) {
      return;
    }

    if (value) {
      let _target = null;
      let _item = null;
      if (item?.items) {
        _target = event.currentTarget;
        _item = item;
      }

      if (!item._parentHover) {
        this.openSubMenu(_target, _item);
      }
      this._menu.hoverItemId = item.id;
    } else if (this._menu.lvDirection === 'horizontal' && this._overlay && this._overlay.overlayRef.overlayElement) {
      this._overlay.overlayRef.overlayElement.firstElementChild.onmouseenter = (): void => {
        this.menuService.mouseEnter$.next(true);
      };
      this._overlay.overlayRef.overlayElement.firstElementChild.onmouseleave = (): void => {
        this.menuService.mouseEnter$.next(false);
      };
    }

    this.menuService.setMouseEnterState(value);
  }

  getExtendConfig(): ExtendOverlayConfig {
    return {
      clickToCloseStrategy: this._menu.lvDirection !== 'vertical' || this._menu.lvCollapsed === false,
      afterOpen: this._afterOpen,
      beforeClose: this._beforeClose,
    };
  }

  _afterOpen = (overlayRef): void => {
    this._animationState = 'show';
    // 在打开的动画执行过程中屏蔽菜单的鼠标事件
    if (overlayRef.overlayRef) {
      this.renderer.addClass(overlayRef.overlayRef.overlayElement, 'no-mouse-events');
    }
    const opensubscribe = this.openState$.subscribe((open): void => {
      if (open) {
        // 打开动画完成之后取消屏蔽
        if (overlayRef.overlayRef.overlayElement) {
          this.renderer.removeClass(overlayRef.overlayRef.overlayElement, 'no-mouse-events');
        }
        opensubscribe.unsubscribe();
      }
    });
    // 设置z-index
    this.renderer.addClass(overlayRef.overlayRef.hostElement, 'lv-menu-z-index');
  };

  _beforeClose = (overlayRef): Promise<any> =>
    new Promise((resolve): void => {
      this.openState$.next(true);
      this._animationState = 'hide';
      // 在消失的动画执行过程中屏蔽菜单的鼠标事件
      if (overlayRef.overlayRef) {
        this.renderer.addClass(overlayRef.overlayRef.overlayElement, 'no-mouse-events');
      }

      const closeSubscribe = this.openState$.subscribe((open): void => {
        if (!open) {
          closeSubscribe.unsubscribe();
          resolve(true);
        }
      });
    });

  itemClick(event, item): void {
    if (item.disabled) {
      event.preventDefault();
      return;
    }

    if (this._menu.lvItemClickClose) {
      if (this._menu.lvDirection === 'vertical' && this._menu.lvCollapsed) {
        this.menuService.mouseEnter$.next(false);
        this.menuService.open$.next(false);
      }

      if (this._menu.lvDirection === 'horizontal' && this.lvMenuLevel !== 0) {
        this._menu.closeNav();
        this.menuService.mouseEnter$.next(false);
        this.menuService.open$.next(false);
      }
    }

    if (item.items) {
      if (!this._menu.lvMultiExpansion) {
        this.collapseOthers(item);
      } else {
        item.expanded = !item.expanded;
      }
    } else {
      this._menu.lvActiveItemId = item.id;
      this._menu.lvActiveItemIdChange.emit(item.id);
      if (!this._menu.lvMultiExpansion) {
        this.collapseOthers(item);
      }

      // 兼容性：在IE下手动触发CSS的回流、重绘，激活兄弟节点伪类样式
      this.renderer.setStyle(this.el.nativeElement, 'background', 'transparent');
    }

    if (item.routerLink) {
      routerNavigate(this.router, item.routerLink);
    }

    if (item.onClick) {
      if (!item._onClickEmitter) {
        item._onClickEmitter = new EventEmitter();
        item._onClickEmitter.subscribe(item.onClick);
      }

      item._onClickEmitter.emit({
        event,
        data: item,
      });
    }

    this._menu.lvItemClick.emit({
      event,
      data: item,
    });
  }

  openSubMenu(target, item): void {
    if (this._overlay?.overlayRef.hasAttached()) {
      this._overlay.close();
    }
    if (this._overlay?.overlayRef.hasAttached()) {
      this._overlay.overlayRef.dispose();
    }
    const flag =
      !item || !item.items || item.disabled || (this._menu.lvDirection === 'horizontal' && this.lvMenuLevel === 1);
    if (flag) {
      return;
    }

    let _position = this.MENU_PANEL_POSITION[0];
    if (this._menu.lvDirection === 'horizontal') {
      if (this.lvMenuLevel < 2) {
        let customPosition = this.MENU_PANEL_POSITION[1];
        if (this._menu.lvMenuPanelAlignment === 'center') {
          customPosition = [...[this.MENU_PANEL_POSITION[1][1]], ...this.MENU_PANEL_POSITION[1]];
        }
        if (this._menu.lvMenuPanelAlignment === 'right') {
          customPosition = [...[this.MENU_PANEL_POSITION[1][2]], ...this.MENU_PANEL_POSITION[1]];
        }
        _position = customPosition;
      } else {
        _position = this.MENU_PANEL_POSITION[2];
      }
    }

    const config = new OverlayConfig({
      panelClass: this._menu.lvDirection === 'horizontal' ? 'lv-top-menu-overlay-panel' : '',
      hasBackdrop: false,
      positionStrategy: this.overlay.position().flexibleConnectedTo(target).withPositions(_position),
    });

    const extendConfig = this.getExtendConfig();

    const portal = new TemplatePortal(this.menuPanelTemplate, this.viewContainerRef, {
      $implicit: item,
      level: this.lvMenuLevel,
    });

    this._overlay = this.overlayService.open(portal, config, extendConfig);

    if (this._menu.lvDirection === 'horizontal' && this.lvMenuLevel === 0) {
      this._menu._nav = this._overlay;
    }
  }
}
